
#include "RtBase.h"
#include "RtHttpProxyManager.h"
#include "RtHttpProxySetting.h"
#include "RtHttpAuthenticator.h"
#ifdef RT_WIN32
#include "RtHttpNTLMAuth.h"
#endif // RT_WIN32
#if (defined RT_MMP /*|| defined RT_PORT_CLIENT*/)
#include "HttpAuthInfoGetterFromEureka_ER3_TP.h"
#endif  //RT_MMP

CRtHttpProxyManager* CRtHttpProxyManager::Instance()
{
	return CRtSingletonT<CRtHttpProxyManager>::Instance();
}

CRtHttpProxyManager::CRtHttpProxyManager()
	: m_BrowserType(IRtHttpProxySetting::BROWSER_OTHER)
	, m_pHttpProxySetting(NULL)
	, m_pHttpAuthInfoGetter(NULL)
{
	RT_INFO_TRACE_THIS("CRtHttpProxyManager::CRtHttpProxyManager");
	m_ProxyInfoCaches.push_back(NULL); // for empty proxy info, such as "DIRECT"
	m_itProxyInfoCached = m_ProxyInfoCaches.end();
	InitGetterArray();
}

CRtHttpProxyManager::~CRtHttpProxyManager()
{
	// TODO: we should release ProxyInfo in network thread.
#if 0
	ProxyInfoArrayType::iterator iter = m_ProxyInfoCaches.begin();
	for ( ; iter != m_ProxyInfoCaches.end(); ++iter) {
		if (*iter)
			(*iter)->ReleaseReference();
	}
#endif
	delete m_pHttpProxySetting;

#ifndef RT_WIN32
	IRtHttpProxyInfoGetter *&pGetter0 = m_GetterArray[0].ParaInOut();
	pGetter0 = NULL;
#endif // !RT_WIN32
}

#ifdef RT_WIN32
static BOOL IsInsFile_s(const CRtString &aFileName)
{
	CRtString::size_type nLen = aFileName.length();
	if (nLen > 4 && !strcmp(aFileName.c_str()+nLen-4, ".ins"))
		return TRUE;
	else
		return FALSE;
}
#endif

BOOL CRtHttpProxyManager::InitGetterArray()
{
#ifdef RT_WIN32
	RT_ASSERTE(!m_pHttpProxySetting);
	m_pHttpProxySetting = new CRtHttpProxySettingWin32();
	if (!m_pHttpProxySetting) {
		RT_ASSERTE(FALSE);
		return FALSE;
	}

	BOOL bRet = m_pHttpProxySetting->GetProxySetting(
		m_strProxyArray[0], 
		m_strProxyArray[1], 
		m_strProxyArray[2], 
		m_strBypass, 
		m_BrowserType);
	if (!bRet) {
		RT_ERROR_TRACE("CRtHttpProxyManager::InitGetterArray,"
			" GetProxySetting() failed!");
		return bRet;
	}

	RT_INFO_TRACE("CRtHttpProxyManager::InitGetterArray,"
		" 0=" << m_strProxyArray[0] << 
		" 1=" << m_strProxyArray[1] << 
		" 2=" << m_strProxyArray[2] <<
		" bypass=" << m_strBypass <<
		" type=" << m_BrowserType);
	
	if (!m_strProxyArray[0].empty()) {
		if (IsInsFile_s(m_strProxyArray[0]))
			m_GetterArray[0] = new CRtHttpProxyInfoGetterInsFile(this);
		else
			m_GetterArray[0] = new CRtHttpProxyInfoGetterPacFile(this);
	}
	if (!m_strProxyArray[1].empty()) {
		if (IsInsFile_s(m_strProxyArray[1]))
			m_GetterArray[1] = new CRtHttpProxyInfoGetterInsFile(this);
		else
			m_GetterArray[1] = new CRtHttpProxyInfoGetterPacFile(this);
	}
	if (!m_strProxyArray[2].empty()) {
		CRtHttpProxyInfoGetterManual *pGetManual = new CRtHttpProxyInfoGetterManual();
		if (pGetManual) {
			pGetManual->AddReference();
			pGetManual->SetBypass(m_strBypass, m_BrowserType);
		}
		m_GetterArray[2] = pGetManual;
		if (pGetManual)
			pGetManual->ReleaseReference();
	}
#else
	m_GetterArray[0] = CRtHttpProxyInfoGetterByUpperLayer::Instance();
#endif // RT_WIN32

	for (int i = 0; i < PROXY_TYPE_NUM; i++) {
		m_bIsDeadArray[i] = FALSE;
		RtResult rv = RT_ERROR_NULL_POINTER;
		if (m_GetterArray[i])
			rv = m_GetterArray[i]->StartUp(m_strProxyArray[i]);
		if (RT_FAILED(rv)) 	{
			if (m_GetterArray[i]) {
				RT_WARNING_TRACE("CRtHttpProxyManager::InitGetterArray,"
					" StartUp() failed! i=" << i <<
					" rv=" << rv);
			}
			m_GetterArray[i] = NULL;
			m_bIsDeadArray[i] = TRUE;
		}
	}
	return TRUE;
}

IRtHttpProxyInfoGetter::NextReturn CRtHttpProxyManager::
GetProxyInfo(const CRtString &aHost, WORD aPort, CRtHttpProxyInfo* &aPi)
{
	CRtMutexGuardT<MutexType> theGuard(m_Mutex);

	if (m_itProxyInfoCached != m_ProxyInfoCaches.end()) {
		aPi = *m_itProxyInfoCached;
		if (aPi)
			aPi->AddReference();
		return IRtHttpProxyInfoGetter::NEXT_SUCCESS;
	}

	for (int i = 0; i < PROXY_TYPE_NUM; i++) {
		if (m_bIsDeadArray[i] || !m_GetterArray[i])
			continue;
		IRtHttpProxyInfoGetter::NextReturn nret;
		nret = m_GetterArray[i]->GetNextProxyInfo(aHost, aPort, aPi);
/*		if (nret == IRtHttpProxyInfoGetter::NEXT_SUCCESS)
			return TRUE;
		else if (nret == IRtHttpProxyInfoGetter::NEXT_WOULDBLCOK)
			return FALSE;
*/
		if (nret != IRtHttpProxyInfoGetter::NEXT_FAILED)
			return nret;
		else
			m_bIsDeadArray[i] = TRUE;
	}

//	return CreateProxyInfo(CRtString(), CRtHttpProxyInfo::HTTP_PROXY, aPi);
	return IRtHttpProxyInfoGetter::NEXT_FAILED;
}

RtResult CRtHttpProxyManager::
ClearCacheProxy(const CRtString &aHost, WORD aPort)
{
	CRtMutexGuardT<MutexType> theGuard(m_Mutex);

	int i;
	m_itProxyInfoCached = m_ProxyInfoCaches.end();
	for (i = 0; i < PROXY_TYPE_NUM; i++) {
		if (!m_bIsDeadArray[i]) {
			CRtAutoPtr<CRtHttpProxyInfo> pProxyInfo;
			IRtHttpProxyInfoGetter::NextReturn nrGet;
			nrGet = GetProxyInfo(aHost, aPort, pProxyInfo.ParaInOut());
			if (nrGet != IRtHttpProxyInfoGetter::NEXT_FAILED)
				break;
			else
				m_bIsDeadArray[i] = TRUE;
		}
	}
	if (i == PROXY_TYPE_NUM) {
//		INFOTRACE("CRtHttpProxyManager::ClearCacheProxy, clear all.");
		for (i = 0; i < PROXY_TYPE_NUM; i++) {
			if (m_GetterArray[i] && m_GetterArray[i]->Reset(aHost, aPort))
				m_bIsDeadArray[i] = FALSE;
			else
				m_bIsDeadArray[i] = TRUE;
		}
		return RT_OK;
	}
	else
		return RT_ERROR_FOUND;
}

BOOL CRtHttpProxyManager::
CreateProxyInfo(const CRtString &aProxy, 
				CRtHttpProxyInfo::PROXY_TYPE aType, 
				CRtHttpProxyInfo *&aPi)
{
	CRtMutexGuardT<MutexType> theGuard(m_Mutex);

	if (aProxy.empty()) {
		m_itProxyInfoCached = m_ProxyInfoCaches.begin();
		aPi = *m_itProxyInfoCached;
		return TRUE;
	}
	
	RtResult rv = RT_ERROR_OUT_OF_MEMORY;
	CRtHttpProxyInfo *pNewProxyInfo = new CRtHttpProxyInfo();
	if (pNewProxyInfo) 
		rv = pNewProxyInfo->Init(aProxy, aType);
	
	if (RT_FAILED(rv)) {
		RT_ERROR_TRACE("CRtHttpProxyManager::CreateProxyInfo, Init() failed!");
		delete pNewProxyInfo;
		pNewProxyInfo = NULL;
	}
	else {
		ProxyInfoArrayType::iterator iter = m_ProxyInfoCaches.begin() + 1;
		for ( ; iter != m_ProxyInfoCaches.end(); ++iter) {
			if ((*iter)->Equal(*pNewProxyInfo)) {
				delete pNewProxyInfo;
				pNewProxyInfo = *iter;
				m_itProxyInfoCached = iter;
				break;
			}
		}
		if (iter == m_ProxyInfoCaches.end()) {
			pNewProxyInfo->AddReference();
			m_ProxyInfoCaches.push_back(pNewProxyInfo);
			m_itProxyInfoCached = m_ProxyInfoCaches.end() - 1;
		}
	}

	aPi = pNewProxyInfo;
	if (aPi) {
		aPi->AddReference();
		return TRUE;
	}
	return FALSE;
}

void CRtHttpProxyManager::OnObserve(LPCSTR aTopic, LPVOID aData)
{
	RT_ASSERTE(!strcmp(aTopic, "HttpProxyInfoGetterFile"));

	ObserversType obvOnCall;
	{
		CRtMutexGuardT<MutexType> theGuard(m_Mutex);
		obvOnCall = m_Observers;
	}
	
	ObserversType::iterator iter = obvOnCall.begin();
	for ( ; iter != obvOnCall.end(); ++iter) {
		IRtObserver *pObserver = *iter;
		RtResult rv = RemoveObserver(pObserver);
		if (RT_SUCCEEDED(rv))
			pObserver->OnObserve("HttpProxyManager");
	}
}

RtResult CRtHttpProxyManager::AddObserver(IRtObserver *aObserver)
{
	RT_ASSERTE_RETURN(aObserver, RT_ERROR_INVALID_ARG);

	// TODO: add IRtEventQueue.
	CRtMutexGuardT<MutexType> theGuard(m_Mutex);
	ObserversType::iterator iter = 
		std::find(m_Observers.begin(), m_Observers.end(), aObserver);
	if (iter == m_Observers.end()) {
		m_Observers.push_back(aObserver);
	return RT_OK;
	}
	else 
		return RT_ERROR_FOUND;
}

RtResult CRtHttpProxyManager::RemoveObserver(IRtObserver *aObserver)
{
	RT_ASSERTE_RETURN(aObserver, RT_ERROR_INVALID_ARG);

	CRtMutexGuardT<MutexType> theGuard(m_Mutex);
	ObserversType::iterator iter = 
		std::find(m_Observers.begin(), m_Observers.end(), aObserver);
	if (iter != m_Observers.end()) {
		m_Observers.erase(iter);
	return RT_OK;
	}
	else
		return RT_ERROR_NOT_FOUND;
}

IRtHttpAuthInfoGetter::GETTER_RESULT CRtHttpProxyManager::
GetAuthInfoFromDialog(const CRtString &aChallenge, CRtHttpProxyInfo *aPi, 
					  IRtObserver *aObserver)
{
	RT_ASSERTE_RETURN(aPi, IRtHttpAuthInfoGetter::GETTER_FAILURE);

	int nType = 2;
	CRtString strScheme = GetSchemeFromChallenge(aChallenge);
	if (!strcasecmp(strScheme.c_str(), CRtHttpBasicAuth::s_pszBasicAuth)
		|| !strcasecmp(strScheme.c_str(), CRtHttpDigestAuth::s_pszDigestAuth)) 
	{
		nType = 0;
	}
#ifdef RT_WIN32
	else if (!strcasecmp(strScheme.c_str(), CRtHttpNTLMAuth::s_pszNTLMAuth)) {
		nType = 1;
	}
#endif // RT_WIN32
	else {
		RT_ASSERTE(FALSE);
		return IRtHttpAuthInfoGetter::GETTER_FAILURE;
	}

	CRtString strHostAndPort(aPi->GetHostName());
	char szPort[16];
	sprintf(szPort, "%hu", aPi->GetPort());
	strHostAndPort += ':';
	strHostAndPort += szPort;

	strHostAndPort += '\n';
	LPCSTR pStrstr = rt_strcaserstr(aChallenge.c_str(), "realm=");
	if (pStrstr) {
		pStrstr += 6;
        if (*pStrstr == '"')
            pStrstr++;
        const char *end = strchr(pStrstr, '"');
        if (!end)
            end = strchr(pStrstr, ' ');
        if (end)
            strHostAndPort.append(pStrstr, end - pStrstr);
        else
            strHostAndPort.append(pStrstr);
	}

	if (!m_pHttpAuthInfoGetter) {
#if (defined RT_MMP /*|| defined RT_PORT_CLIENT*/)
		m_pHttpAuthInfoGetter = CHttpAuthInfoGetterFromEureka_ER3_TP::Instance();
#else
#ifdef RT_WIN32
		m_pHttpAuthInfoGetter = CRtHttpAuthInfoGetterWin32::Instance();
#else
		m_pHttpAuthInfoGetter = CRtHttpAuthInfoGetterByUpperLayer::Instance();
#endif
#endif // RT_WIN32
		if (!m_pHttpAuthInfoGetter) {
			RT_ERROR_TRACE("CRtHttpProxyManager::GetAuthInfoFromDialog,"
				" can't init dialog.");
			return IRtHttpAuthInfoGetter::GETTER_FAILURE;
		}
	}

	CRtString strUserName, strPassword;
	IRtHttpAuthInfoGetter::GETTER_RESULT wr;
	wr = m_pHttpAuthInfoGetter->GetAuthInfoBlocked(
		strHostAndPort, nType, strUserName, strPassword, aObserver);
#if (defined RT_MMP/* || defined RT_PORT_CLIENT*/)
	if(IRtHttpAuthInfoGetter::GETTER_OK != wr)
	{
#ifdef RT_WIN32
		m_pHttpAuthInfoGetter = CRtHttpAuthInfoGetterWin32::Instance();
#else
		m_pHttpAuthInfoGetter = CRtHttpAuthInfoGetterByUpperLayer::Instance();
#endif
		if (!m_pHttpAuthInfoGetter) {
			RT_ERROR_TRACE("CRtHttpProxyManager::GetAuthInfoFromDialog,"
				" can't init dialog.");
			return IRtHttpAuthInfoGetter::GETTER_FAILURE;
		}
		wr = m_pHttpAuthInfoGetter->GetAuthInfoBlocked(
			strHostAndPort, nType, strUserName, strPassword, aObserver);
	}
#endif

	if (wr == IRtHttpAuthInfoGetter::GETTER_OK) {
		RT_INFO_TRACE("CRtHttpProxyManager::GetAuthInfoFromDialog,"
			" user inputted the name and password.");
		aPi->SetUserName(strUserName); 
		aPi->SetPasswd(strPassword);
	} 
	else if (wr == IRtHttpAuthInfoGetter::GETTER_INTEREPT) {
		RT_WARNING_TRACE("CRtHttpProxyManager::GetAuthInfoFromDialog,"
			" user interepted the dialog.");
	}
	else if (wr == IRtHttpAuthInfoGetter::GETTER_CANCEL) {
		RT_WARNING_TRACE("CRtHttpProxyManager::GetAuthInfoFromDialog,"
			" user canneled the dialog.");
	}
	return wr;
}

CRtString CRtHttpProxyManager::
GetSchemeFromChallenge(const CRtString &aChallenge)
{
	CRtString strScheme;
	CRtString::size_type nFind= aChallenge.find(' ');
	if (nFind != CRtString::npos)
		strScheme = aChallenge.substr(0, nFind);
	else
		strScheme = aChallenge;
	return strScheme;
}

IRtHttpAuthInfoGetter* CRtHttpProxyManager::GetHttpAuthInfoGetter()
{
	return m_pHttpAuthInfoGetter;
}

BOOL CRtHttpProxyManager::IsBrowerProxySet()
{
	for (int i = 0; i < PROXY_TYPE_NUM; i++) {
		if (m_GetterArray[i])
			return TRUE;
	}
	return FALSE;
}
